package com.fary.web.method.annotation;

import com.fary.beans.BeanUtils;
import com.fary.beans.factory.config.ConfigurableBeanFactory;
import com.fary.core.MethodParameter;
import com.fary.core.SpringException;
import com.fary.core.convert.ConversionService;
import com.fary.core.convert.TypeDescriptor;
import com.fary.util.Assert;
import com.fary.util.StringUtils;
import com.fary.web.bind.annotation.RequestParam;
import com.fary.web.bind.annotation.RequestPart;
import com.fary.web.bind.annotation.ValueConstants;
import com.fary.web.context.request.NativeWebRequest;
import com.fary.web.method.support.UriComponentsContributor;
import com.fary.web.multipart.MultipartFile;
import com.fary.web.multipart.MultipartRequest;
import com.fary.web.multipart.support.MultipartResolutionDelegate;
import com.fary.web.util.UriComponentsBuilder;
import jdk.internal.jline.internal.Nullable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver implements UriComponentsContributor {

    private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);

    /**
     * 这个参数老重要了：
     * 	true：表示参数类型是基本类型 参考BeanUtils#isSimpleProperty(什么Enum、Number、Date、URL、包装类型、以上类型的数组类型等等)
     * 	如果是基本类型，即使你不写@RequestParam注解，它也是会走进来处理的~~~(这个@PathVariable可不会哟~)
     * 	fasle：除上以外的。  要想它处理就必须标注注解才行哦，比如List等~
     * 	默认值是false
     * 	默认会添加2个参数处理器，一个false、一个true
     */
    private final boolean useDefaultResolution;

    public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
        this.useDefaultResolution = useDefaultResolution;
    }

    // 传入了ConfigurableBeanFactory ，所以它支持处理占位符${...} 并且支持SpEL了
    // 此构造都在RequestMappingHandlerAdapter里调用，最后都会传入true来Catch-all Case  这种设计挺有意思的
    public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) {
        super(beanFactory);
        this.useDefaultResolution = useDefaultResolution;
    }

    // 此处理器能处理如下Case：
    // 1、所有标注有@RequestParam注解的类型（非Map）/ 注解指定了value值的Map类型（自己提供转换器哦）
    // ======下面都表示没有标注@RequestParam注解了的=======
    // 1、不能标注有@RequestPart注解，否则直接不处理了
    // 2、是上传的request：isMultipartArgument() = true（MultipartFile类型或者对应的集合/数组类型  或者javax.servlet.http.Part对应结合/数组类型）
    // 3、useDefaultResolution=true情况下，"基本类型"也会处理
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
                return (requestParam != null && StringUtils.hasText(requestParam.name()));
            } else {
                return true;
            }
        } else {
            if (parameter.hasParameterAnnotation(RequestPart.class)) {
                return false;
            }
            parameter = parameter.nestedIfOptional();
            if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
                return true;
            } else if (this.useDefaultResolution) {
                // useDefaultResolution=true情况下，"基本类型"也会处理
                return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
            } else {
                return false;
            }
        }
    }

    /**
     * 从这也可以看出：即使木有@RequestParam注解，也是可以创建出一个NamedValueInfo来的
     */
    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
        return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
    }

    /**
     * 核心方法：根据Name 获取值（普通/文件上传）
     * 并且还有集合、数组等情况
     */
    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

        // 这块解析出来的是个MultipartFile或者其集合/数组（Part）
        if (servletRequest != null) {
            Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
            if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
                return mpArg;
            }
        }

        Object arg = null;
        MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
        if (multipartRequest != null) {
            List<MultipartFile> files = multipartRequest.getFiles(name);
            if (!files.isEmpty()) {
                arg = (files.size() == 1 ? files.get(0) : files);
            }
        }
        // 若解析出来值仍旧为null，那处理完文件上传里木有，那就去参数里取吧
        // 由此可见：文件上传的优先级是高于请求参数的
        if (arg == null) {
            String[] paramValues = request.getParameterValues(name);
            if (paramValues != null) {
                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
            }
        }
        return arg;
    }

    @Override
    protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {

        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            if (servletRequest == null || !MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
                throw new SpringException("Current request is not a multipart request");
            } else {
                throw new SpringException(name);
            }
        } else {
            throw new SpringException(name);
        }
    }

    @Override
    public void contributeMethodArgument(MethodParameter parameter, Object value,
                                         UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {

        Class<?> paramType = parameter.getNestedParameterType();
        if (Map.class.isAssignableFrom(paramType) || MultipartFile.class == paramType || Part.class == paramType) {
            return;
        }

        RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
        String name = (requestParam != null && StringUtils.hasLength(requestParam.name()) ?
                requestParam.name() : parameter.getParameterName());
        Assert.state(name != null, "Unresolvable parameter name");

        parameter = parameter.nestedIfOptional();
        if (value instanceof Optional) {
            value = ((Optional<?>) value).orElse(null);
        }

        if (value == null) {
            if (requestParam != null &&
                    (!requestParam.required() || !requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE))) {
                return;
            }
            builder.queryParam(name);
        } else if (value instanceof Collection) {
            for (Object element : (Collection<?>) value) {
                element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element);
                builder.queryParam(name, element);
            }
        } else {
            builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value));
        }
    }

    @Nullable
    protected String formatUriValue(
            ConversionService cs, TypeDescriptor sourceType, Object value) {

        if (value == null) {
            return null;
        } else if (value instanceof String) {
            return (String) value;
        } else if (cs != null) {
            return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
        } else {
            return value.toString();
        }
    }


    private static class RequestParamNamedValueInfo extends NamedValueInfo {

        /**
         * 不写@RequestParam，那么就会用这个默认值
         * 注意：required = false的哟（若写了注解，required默认可是true，请务必注意区分）
         */
        public RequestParamNamedValueInfo() {
            super("", false, ValueConstants.DEFAULT_NONE);
        }

        public RequestParamNamedValueInfo(RequestParam annotation) {
            super(annotation.name(), annotation.required(), annotation.defaultValue());
        }
    }

}